﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RouterControl.LocalDb;
using Microsoft.HomeServer.SDK.Interop.v1;
using RouterControl.Whs;
using RouterControl.Whs.SingletonService;

namespace RouterControl.Service.DeviceSlaves
{
    [RouterSlave]
    public sealed class PortForwardingSlave : RouterSlave
    {

        private volatile CommonInternetGateway _router;
        private List<CommonInternetGateway.PortForwarding> _currentForwardings;
        private List<string> _approvedForwardingIds = new List<string>();
        private List<Whs.WhsNotification> _unapprovedNotifications = new List<Whs.WhsNotification>();
        private readonly object _checkApprovalStatusLock = new object();
        private CommonInternetGateway.PortForwarding[] _lastForwardings;

        public IList<string> ApprovedForwardingsIds
        {
            get { return _approvedForwardingIds.AsReadOnly(); }
        }

        public IList<CommonInternetGateway.PortForwarding> CurrentForwardings
        {
            get
            {
                if (_currentForwardings == null)
                    return null;

                return _currentForwardings.AsReadOnly();
            }
        }

        public IList<CommonInternetGateway.PortForwarding> GetCurrentForwardings(ApprovalStatus status)
        {
            if (_currentForwardings == null)
                return new List<CommonInternetGateway.PortForwarding>().AsReadOnly();

            if (status == ApprovalStatus.All)
                return _currentForwardings;

            // determine forwardings
            var cur = _currentForwardings.ToArray();
            var appr = _approvedForwardingIds.ToArray();
            var ret = new List<CommonInternetGateway.PortForwarding>();

            // check approved
            if ((status & ApprovalStatus.Approved) == ApprovalStatus.Approved)
            {
                ret.AddRange(
                    from c in cur
                    where appr.Contains(c.Id)
                    select c
                    );
            }

            // check unapproved
            if ((status & ApprovalStatus.Unapproved) == ApprovalStatus.Unapproved)
            {
                ret.AddRange(
                    from c in cur
                    where !appr.Contains(c.Id)
                    select c
                    );
            }

            return ret.AsReadOnly();
        }

        public IList<PortForwardingEx> GetCurrentForwardingsEx()
        {
            if (_currentForwardings == null)
                return new List<PortForwardingEx>().AsReadOnly();

            // determine forwardings
            var cur = _currentForwardings.ToArray();
            var appr = _approvedForwardingIds.ToArray();
            var ret = new List<PortForwardingEx>();

            // check approved
            foreach (var c in cur)
            {
                ret.Add(new PortForwardingEx()
                    {
                        Forwarding = c,
                        Approved = appr.Contains(c.Id)

                    });
            }

            return ret.AsReadOnly();
        }

        public void AddOrUpdateForwarding(CommonInternetGateway.PortForwarding fwd)
        {
            if (_router == null)
                throw new Exception("no router present in port forwarding; no router selected?");

            _router.AddOrUpdatePortForwarding(fwd);
        }

        public void DeleteForwarding(CommonInternetGateway.PortForwarding fwd)
        {
            if (_router == null)
                throw new Exception("no router present in port forwarding; no router selected?");

            _router.DeletePortForwarding(fwd);
        }

        public override void OnDataUpdated()
        {
            base.OnDataUpdated();

            // continue
            try
            {
                GatherPortForwardings();

                CheckApprovalStatus();
            }
            catch (Exception x)
            {
                _log.Warn("Failed to gather port forwardings", x);

                throw;
            }
        }

        private void GatherPortForwardings()
        {
            var router = _router;

            if ((router == null) || !Properties.Settings.Default.EnablePortForwardingMonitoring)
                return;

            // gather forwards
            var newFwds = router.GetPortForwardings();

            _currentForwardings = new List<CommonInternetGateway.PortForwarding>(newFwds);

        }

        public override void OnRouterChanged(CommonInternetGateway newRouter)
        {
            base.OnRouterChanged(newRouter);

            _router = null;

            // handle change
            _currentForwardings = null;
            _lastForwardings = null;
            _router = newRouter;

            CheckApprovalStatus();
        }

        public void CheckApprovalStatus()
        {
            lock (_checkApprovalStatusLock)
            {
                try
                {
	                CheckApprovalStatusInternal();
                }
                catch (Exception ex)
                {
                    _log.Error("Failed to check port forwarding approval status", ex);

                    throw;
                }
            }
        }

        private void CheckApprovalStatusInternal()
        {
            if ((_currentForwardings == null) || 
                !Properties.Settings.Default.EnablePortForwardingNotifications || 
                !Properties.Settings.Default.EnablePortForwardingMonitoring)
            {
                // ensure no notifications are shown
                if (_unapprovedNotifications.Count > 0)
                {
                    var nn = _unapprovedNotifications.ToArray();

                    foreach (var n in nn)
                    {
                        n.HideNotification();

                        _unapprovedNotifications.Remove(n);
                    }
                }

                // continue
                return;
            }

            // persist
            var cur = _currentForwardings.ToArray();
            var appr = _approvedForwardingIds.ToArray();

            // check unapproved
            var unapproved = (
                from c in cur
                where !appr.Contains(c.Id) && c.Enabled
                select c
                ).ToList();

            // remove notifications for approved or 
            // removed port forwardings
            {
                var approvedOrRemoved = (
                    from n in _unapprovedNotifications
                    where unapproved.FindIndex((a) => (a.Id == (string)n.Tag)) < 0
                    select n
                    ).ToArray();    // persist

                foreach (var n in approvedOrRemoved)
                {
                    n.HideNotification();

                    _unapprovedNotifications.Remove(n);
                }
            }

            // add notifications for unapproved port forwardings
            {
	            var unappprovedWithoutNotification = (
	                from u in unapproved
	                where _unapprovedNotifications.FindIndex((a) => (u.Id == (string)a.Tag)) < 0
	                select u
	                ).ToArray();    // persist
	
	            foreach (var pf in unappprovedWithoutNotification)
	            {
	                var n = new Whs.WhsNotification(
	                    Whs.Properties.Constants.PortForwardingNotificationIdPrefix + pf.Id
	                    )
	                    {
	                        Severity = WHS_Notification_Severity.WHS_ERROR,
	                        Tag = pf.Id,
	                        Title = string.Format(
	                            Properties.Resources.txtUnapprovedPortforwardingTitle,
	                            pf.ToString()
	                            ),
	                        Description = string.Format(
	                            Properties.Resources.txtUnapprovedPortforwardingDesc,
	                            pf.ToString()
	                            ),
	
	                    };
	
	                n.ShowNotifcation();
	
	                _unapprovedNotifications.Add(n);
	            }
            }

            // add timed notifications for new but approved port forwardings
            {
                // check new but approved
                var newButApproved = (
                    from c in cur
                    where c.Enabled && appr.Contains(c.Id)
                    && (_lastForwardings == null || Array.FindIndex(_lastForwardings, (a) => a.Enabled && (a.Id == c.Id)) < 0)
                    select c
                    );

                // create notifications
                foreach (var pf in newButApproved)
                {
                    var timeout = Properties.Settings.Default.DefaultUserNotificationShowTime;

                    var n = new Whs.WhsNotification(
                        Whs.Properties.Constants.PortForwardingNotificationIdPrefix + pf.Id
                        )
                    {
                        Severity = WHS_Notification_Severity.WHS_WARNING,
                        Tag = pf.Id,
                        Title = string.Format(
                            Properties.Resources.txtNewApprovedPortforwardingTitle,
                            pf.ToString(),
                            timeout.TotalSeconds
                            ),
                        Description = string.Format(
                            Properties.Resources.txtNewApprovedPortforwardingDesc,
                            pf.ToString(),
                            timeout.TotalSeconds
                            ),

                    };

                    n.ShowNotifcation(timeout);
                }
            }

            // add timed notifications for old port forwardings
            if (_lastForwardings != null)
            {
	            // check old (approved and unapproved)
	            var old = (
	                from l in _lastForwardings
                    where l.Enabled && Array.FindIndex(cur, (c) => c.Enabled && (c.Id == l.Id)) < 0
	                select l
	                );
	
	            // create notifications
	            foreach (var pf in old)
	            {
	                var timeout = Properties.Settings.Default.DefaultUserNotificationShowTime;
	
	                var n = new Whs.WhsNotification(
	                    Whs.Properties.Constants.PortForwardingNotificationIdPrefix + pf.Id
	                    )
	                {
	                    Severity = WHS_Notification_Severity.WHS_WARNING,
	                    Tag = pf.Id,
	                    Title = string.Format(
	                        Properties.Resources.txtOldPortforwardingTitle,
	                        pf.ToString(),
	                        timeout.TotalSeconds
	                        ),
	                    Description = string.Format(
	                        Properties.Resources.txtOldPortforwardingDesc,
	                        pf.ToString(),
	                        timeout.TotalSeconds
	                        ),
	
	                };
	
	                n.ShowNotifcation(timeout);
	            }
            }

            // finalize
            _lastForwardings = cur;
        }
        private void LoadApprovedForwardings()
        {
            try
            {
	            var s = Properties.States.Default.ApprovedPortFowardings;
	
	            if (string.IsNullOrEmpty(s))
	            {
	                _approvedForwardingIds = new List<string>();
	
	                return;
	            }
	
	            // load ids
	            var af = s.Split(
	                new [] { ';' },
	                StringSplitOptions.RemoveEmptyEntries
	                );
	
	            _approvedForwardingIds = new List<string>(af);
            }
            catch (Exception ex)
            {
                _log.Error("Failed to load approved port forwardings", ex);

                throw;
            }
        }

        private void SaveApprovedForwardings()
        {
            try
            {
	            Properties.States.Default.ApprovedPortFowardings = string.Join(
	                ";",
	                _approvedForwardingIds.ToArray()
	                );
	
	            Properties.States.Default.Save();
            }
            catch (Exception ex)
            {
                _log.Error("Failed to save approved port forwardings", ex);

                throw;
            }
        }

        public void ApproveForwarding(string fwdId)
        {
            if (string.IsNullOrEmpty(fwdId))
                throw new ArgumentException("null or empty", "fwdId");

            try
            {
                if (!_approvedForwardingIds.Contains(fwdId))
                {
                    _approvedForwardingIds.Add(fwdId);

                    SaveApprovedForwardings();
                }
            }
            catch (Exception ex)
            {
                _log.Error("Failed to approve port forwarding: " + fwdId, ex);

                throw;
            }
        }

        public void DisapproveForwarding(string fwdId)
        {
            if (string.IsNullOrEmpty(fwdId))
                throw new ArgumentException("null or empty", "fwdId");

            try
            {
                if (_approvedForwardingIds.Contains(fwdId))
                {
                    _approvedForwardingIds.Remove(fwdId);

                    SaveApprovedForwardings();
                }
            }
            catch (Exception ex)
            {
                _log.Error("Failed to disapprove port forwarding: " + fwdId, ex);

                throw;
            }
        }

        public void DisapproveAllForwardings()
        {
            try
            {
                _approvedForwardingIds.Clear();

                SaveApprovedForwardings();
            }
            catch (Exception ex)
            {
                _log.Error("Failed to disapprove al port forwardings", ex);

                throw;
            }
        }

        public PortForwardingSlave()
        {
            LoadApprovedForwardings();
        }
    }
}
